package com.ssm.basis.web.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ssm.basis.model.pojo.dto.ServletErrorDto;
import com.ssm.basis.service.exception.CustomException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.WebUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Objects;

import static org.springframework.web.util.WebUtils.*;

@Slf4j
public abstract class RequestUtil {
    static ObjectMapper objectMapper;

    /**
     * 获取当前请求
     */
    public static HttpServletRequest getCurrentRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }

    /**
     * 获取 Session Id
     *
     * @see WebUtils#getSessionId(HttpServletRequest)
     */
    public static String getSessionId(HttpServletRequest request) {
        String sessionId = null;
        // request 为空返回 null
        if (Objects.isNull(request)) {
            return sessionId;
        }
        // 优先取请求里的 JSSESSIONID，如果为空，需要新建一个
        if (!StringUtils.hasLength(sessionId = request.getRequestedSessionId())) {
            // 不为空
            if (Objects.nonNull(request.getSession())) {
                sessionId = WebUtils.getSessionId(request);
            }
        }
        return sessionId;
    }

    public static HttpServletRequest requestWrite(ServletRequest request, Object body) {
        try (final ServletInputStream is = request.getInputStream()) {
            return new HttpServletRequestWrapper((HttpServletRequest) request) {
                @Override
                public ServletInputStream getInputStream() throws IOException {
                    // byte[] bodyBytes = SerializationUtils.serialize(body);
                    // byte[] bodyBytes = objectMapper.writeValueAsString(body).getBytes(getCharacterEncoding());
                    byte[] bodyBytes = objectMapper.writeValueAsBytes(body);
                    final ByteArrayInputStream bais = new ByteArrayInputStream(bodyBytes);
                    return new ServletInputStreamWrapper(is, bais);
                }
            };
        } catch (IOException e) {
            throw new CustomException(e);
        }
    }

    public static ServletErrorDto getErrorMessage(HttpServletRequest request) {
        Object message = request.getAttribute(ERROR_MESSAGE_ATTRIBUTE);
        Object exception = request.getAttribute(ERROR_EXCEPTION_ATTRIBUTE);
        Object statusCode = request.getAttribute(ERROR_STATUS_CODE_ATTRIBUTE);
        Object requestUri = request.getAttribute(ERROR_REQUEST_URI_ATTRIBUTE);
        Object exceptionType = request.getAttribute(ERROR_EXCEPTION_TYPE_ATTRIBUTE);

        log.error("statusCode: {}, exceptionType: {}, message: {}, path: {}, exception: {}",
                statusCode, exceptionType, message, requestUri, exception);

        return ServletErrorDto.builder()
                .message(String.valueOf(message))
                .exception(String.valueOf(exception))
                .statusCode(String.valueOf(statusCode))
                .requestUri(String.valueOf(requestUri))
                .exceptionType(String.valueOf(exceptionType))
                .build();
    }

    @Autowired
    void setObjectMapper(ObjectMapper objectMapper) {
        RequestUtil.objectMapper = objectMapper;
    }

    @AllArgsConstructor
    static class ServletInputStreamWrapper extends ServletInputStream {
        ServletInputStream sis;
        ByteArrayInputStream bais;

        @Override
        public boolean isFinished() {
            return sis.isFinished();
        }

        @Override
        public boolean isReady() {
            return sis.isReady();
        }

        @Override
        public void setReadListener(ReadListener listener) {
            sis.setReadListener(listener);
        }

        @Override
        public int read() throws IOException {
            return bais.read();
        }
    }
}